/* eslint-disable no-bitwise, no-mixed-operators, no-use-before-define, max-len */
import { BigInteger, SecureRandom } from 'jsbn';
import ECCurveFp from './ec';

const rng = new SecureRandom();
const {curve, G, n} = generateEcparam();

/**
 * 获取公共椭圆曲线
 * @returns {ECCurveFp} 椭圆曲线对象
 */
function getGlobalCurve() {
  return curve;
}

/**
 * 生成椭圆曲线参数
 * @returns {Object} 包含曲线、基点和阶的参数对象
 */
function generateEcparam() {
  // 椭圆曲线
  const p = new BigInteger('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF', 16);
  const a = new BigInteger('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC', 16);
  const b = new BigInteger('28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93', 16);
  const curve = new ECCurveFp(p, a, b);

  // 基点
  const gxHex = '32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7';
  const gyHex = 'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0';
  const G = curve.decodePointHex('04' + gxHex + gyHex);

  const n = new BigInteger('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123', 16);

  return {curve, G, n};
}

/**
 * 生成密钥对：publicKey = privateKey * G
 * @param {number} a - 随机数参数a
 * @param {number} b - 随机数参数b
 * @param {number} c - 随机数参数c
 * @returns {Object} 包含私钥和公钥的对象
 */
function generateKeyPairHex(a, b, c) {
  const random = a ? new BigInteger(a, b, c) : new BigInteger(n.bitLength(), rng);
  const d = random.mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE); // 随机数
  const privateKey = leftPad(d.toString(16), 64);

  const P = G.multiply(d); // P = dG，p 为公钥，d 为私钥
  const Px = leftPad(P.getX().toBigInteger().toString(16), 64);
  const Py = leftPad(P.getY().toBigInteger().toString(16), 64);
  const publicKey = '04' + Px + Py;

  return {privateKey, publicKey};
}

/**
 * 生成压缩公钥
 * @param {string} s - 原始公钥
 * @returns {string} 压缩后的公钥
 */
function compressPublicKeyHex(s) {
  if (s.length !== 130) throw new Error('Invalid public key to compress');

  const len = (s.length - 2) / 2;
  const xHex = s.substr(2, len);
  const y = new BigInteger(s.substr(len + 2, len), 16);

  let prefix = '03';
  if (y.mod(new BigInteger('2')).equals(BigInteger.ZERO)) prefix = '02';

  return prefix + xHex;
}

/**
 * UTF-8 字符串转十六进制字符串
 * @param {string} input - UTF-8 字符串
 * @returns {string} 十六进制字符串
 */
function utf8ToHex(input) {
  input = unescape(encodeURIComponent(input));

  const length = input.length;

  // 转换到字数组
  const words = [];
  for (let i = 0; i < length; i++) {
    words[i >>> 2] |= (input.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
  }

  // 转换到16进制
  const hexChars = [];
  for (let i = 0; i < length; i++) {
    const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
    hexChars.push((bite >>> 4).toString(16));
    hexChars.push((bite & 0x0f).toString(16));
  }

  return hexChars.join('');
}

/**
 * 补全十六进制字符串
 * @param {string} input - 输入字符串
 * @param {number} num - 目标长度
 * @returns {string} 补全后的字符串
 */
function leftPad(input, num) {
  if (input.length >= num) return input;

  return (new Array(num - input.length + 1)).join('0') + input;
}

/**
 * 字节数组转十六进制字符串
 * @param {Array} arr - 字节数组
 * @returns {string} 十六进制字符串
 */
function arrayToHex(arr) {
  return arr.map(item => {
    item = item.toString(16);
    return item.length === 1 ? '0' + item : item;
  }).join('');
}

/**
 * 十六进制字符串转字节数组
 * @param {string} str - 十六进制字符串
 * @returns {Array} 字节数组
 */
function hexToArray(str) {
  const arr = [];
  for (let i = 0, len = str.length; i < len; i += 2) {
    arr.push(parseInt(str.substr(i, 2), 16));
  }
  return arr;
}

/**
 * UTF-8 字符串转字节数组
 * @param {string} str - UTF-8 字符串
 * @returns {Array} 字节数组
 */
function utf8ToArray(str) {
  const arr = [];

  for (let i = 0, len = str.length; i < len; i++) {
    const point = str.codePointAt(i);

    if (point <= 0x007f) {
      // 单字节，标量值：00000000 00000000 0zzzzzzz
      arr.push(point);
    } else if (point <= 0x07ff) {
      // 双字节，标量值：00000000 00000yyy yyzzzzzz
      arr.push(0xc0 | (point >>> 6)); // 110yyyyy（0xc0-0xdf）
      arr.push(0x80 | (point & 0x3f)); // 10zzzzzz（0x80-0xbf）
    } else if (point <= 0xD7FF || (point >= 0xE000 && point <= 0xFFFF)) {
      // 三字节：标量值：00000000 xxxxyyyy yyzzzzzz
      arr.push(0xe0 | (point >>> 12)); // 1110xxxx（0xe0-0xef）
      arr.push(0x80 | ((point >>> 6) & 0x3f)); // 10yyyyyy（0x80-0xbf）
      arr.push(0x80 | (point & 0x3f)); // 10zzzzzz（0x80-0xbf）
    } else if (point >= 0x010000 && point <= 0x10FFFF) {
      // 四字节：标量值：000wwwxx xxxxyyyy yyzzzzzz
      i++;
      arr.push((0xf0 | (point >>> 18) & 0x1c)); // 11110www（0xf0-0xf7）
      arr.push((0x80 | ((point >>> 12) & 0x3f))); // 10xxxxxx（0x80-0xbf）
      arr.push((0x80 | ((point >>> 6) & 0x3f))); // 10yyyyyy（0x80-0xbf）
      arr.push((0x80 | (point & 0x3f))); // 10zzzzzz（0x80-0xbf）
    } else {
      // 五、六字节，暂时不支持
      arr.push(point);
      throw new Error('input is not supported');
    }
  }

  return arr;
}

/**
 * 字节数组转 UTF-8 字符串
 * @param {Array} arr - 字节数组
 * @returns {string} UTF-8 字符串
 */
function arrayToUtf8(arr) {
  const str = [];
  for (let i = 0, len = arr.length; i < len; i++) {
    if (arr[i] >= 0xf0 && arr[i] <= 0xf7) {
      // 四字节
      str.push(String.fromCodePoint(((arr[i] & 0x07) << 18) + ((arr[i + 1] & 0x3f) << 12) + ((arr[i + 2] & 0x3f) << 6) + (arr[i + 3] & 0x3f)));
      i += 3;
    } else if (arr[i] >= 0xe0 && arr[i] <= 0xef) {
      // 三字节
      str.push(String.fromCodePoint(((arr[i] & 0x0f) << 12) + ((arr[i + 1] & 0x3f) << 6) + (arr[i + 2] & 0x3f)));
      i += 2;
    } else if (arr[i] >= 0xc0 && arr[i] <= 0xdf) {
      // 双字节
      str.push(String.fromCodePoint(((arr[i] & 0x1f) << 6) + (arr[i + 1] & 0x3f)));
      i++;
    } else {
      // 单字节
      str.push(String.fromCodePoint(arr[i]));
    }
  }

  return str.join('');
}

/**
 * 验证公钥是否为椭圆曲线上的点
 * @param {string} publicKey - 公钥
 * @returns {boolean} 是否为椭圆曲线上的点
 */
function verifyPublicKey(publicKey) {
  const point = curve.decodePointHex(publicKey);
  if (!point) return false;

  const x = point.getX();
  const y = point.getY();

  // 验证 y^2 是否等于 x^3 + ax + b
  return y.square().equals(x.multiply(x.square()).add(x.multiply(curve.a)).add(curve.b));
}

/**
 * 验证两个公钥是否等价
 * @param {string} publicKey1 - 第一个公钥
 * @param {string} publicKey2 - 第二个公钥
 * @returns {boolean} 是否等价
 */
function comparePublicKeyHex(publicKey1, publicKey2) {
  const point1 = curve.decodePointHex(publicKey1);
  if (!point1) return false;

  const point2 = curve.decodePointHex(publicKey2);
  if (!point2) return false;

  return point1.equals(point2);
}

export default {
  /**
   * 获取公共椭圆曲线
   */
  getGlobalCurve,
  
  /**
   * 生成椭圆曲线参数
   */
  generateEcparam,
  
  /**
   * 生成密钥对
   */
  generateKeyPairHex,
  
  /**
   * 生成压缩公钥
   */
  compressPublicKeyHex,
  
  /**
   * UTF-8 字符串转十六进制字符串
   */
  utf8ToHex,
  
  /**
   * 补全十六进制字符串
   */
  leftPad,
  
  /**
   * 字节数组转十六进制字符串
   */
  arrayToHex,
  
  /**
   * 十六进制字符串转字节数组
   */
  hexToArray,
  
  /**
   * 字节数组转 UTF-8 字符串
   */
  arrayToUtf8,
  
  /**
   * UTF-8 字符串转字节数组
   */
  utf8ToArray,
  
  /**
   * 验证公钥是否为椭圆曲线上的点
   */
  verifyPublicKey,
  
  /**
   * 验证两个公钥是否等价
   */
  comparePublicKeyHex
};